home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / Pakiet bezpieczenstwa / mini Pentoo LiveCD 2006.1 / mpentoo-2006.1.iso / livecd.squashfs / usr / lib / mozilla-firefox / components / nsXmlRpcClient.js < prev    next >
Text File  |  2006-05-08  |  50KB  |  1,428 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Mozilla XML-RPC Client component.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Digital Creations 2, Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2000
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Martijn Pieters <mj@digicool.com> (original author)
  23.  *   Samuel Sieb <samuel@sieb.net> brought it up to date with
  24.  *   current APIs and added authentication
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. /*
  41.  *  nsXmlRpcClient XPCOM component
  42.  *  Version: $Revision: 1.34.28.1 $
  43.  *
  44.  *  $Id: nsXmlRpcClient.js,v 1.34.28.1 2005/10/18 15:49:54 silver%warwickcompsoc.co.uk Exp $
  45.  */
  46.  
  47. /*
  48.  * Constants
  49.  */
  50. const XMLRPCCLIENT_CONTRACTID = '@mozilla.org/xml-rpc/client;1';
  51. const XMLRPCCLIENT_CID =
  52.     Components.ID('{37127241-1e6e-46aa-ba87-601d41bb47df}');
  53. const XMLRPCCLIENT_IID = Components.interfaces.nsIXmlRpcClient;
  54.  
  55. const XMLRPCFAULT_CONTRACTID = '@mozilla.org/xml-rpc/fault;1';
  56. const XMLRPCFAULT_CID =
  57.     Components.ID('{691cb864-0a7e-448c-98ee-4a7f359cf145}');
  58. const XMLRPCFAULT_IID = Components.interfaces.nsIXmlRpcFault;
  59.  
  60. const DEBUG = false;
  61. const DEBUGPARSE = false;
  62.  
  63. /*
  64.  * Class definitions
  65.  */
  66.  
  67. /* The nsXmlRpcFault class constructor. */
  68. function nsXmlRpcFault() {}
  69.  
  70. /* the nsXmlRpcFault class def */
  71. nsXmlRpcFault.prototype = {
  72.     faultCode: 0,
  73.     faultString: '',
  74.  
  75.     init: function(faultCode, faultString) {
  76.         this.faultCode = faultCode;
  77.         this.faultString = faultString;
  78.     },
  79.  
  80.     toString: function() {
  81.         return '<XML-RPC Fault: (' + this.faultCode + ') ' +
  82.             this.faultString + '>';
  83.     },
  84.  
  85.     // nsISupports interface
  86.     QueryInterface: function(iid) {
  87.         if (!iid.equals(Components.interfaces.nsISupports) &&
  88.             !iid.equals(XMLRPCFAULT_IID))
  89.             throw Components.results.NS_ERROR_NO_INTERFACE;
  90.         return this;
  91.     }
  92. };
  93.  
  94. /* The nsXmlRpcClient class constructor. */
  95. function nsXmlRpcClient() {}
  96.  
  97. /* the nsXmlRpcClient class def */
  98. nsXmlRpcClient.prototype = {
  99.     _serverUrl: null,
  100.     _useAuth: false,
  101.     _passwordTried: false,
  102.  
  103.     init: function(serverURL) {
  104.         var ios = Components.classes["@mozilla.org/network/io-service;1"].
  105.             getService(Components.interfaces.nsIIOService);
  106.         var oURL = ios.newURI(serverURL, null, null);
  107.  
  108.         // Make sure it is a complete spec
  109.         // Note that we don't care what the scheme is otherwise.
  110.         // Should we care? POST works only on http and https..
  111.         if (!oURL.scheme) oURL.scheme = 'http';
  112.         if ((oURL.scheme != 'http') && (oURL.scheme != 'https'))
  113.             throw Components.Exception('Only HTTP is supported');
  114.  
  115.         this._serverUrl = oURL;
  116.     },
  117.  
  118.     setAuthentication: function(username, password){
  119.         if ((typeof username == "string") &&
  120.             (typeof password == "string")){
  121.           this._useAuth = true;
  122.           this._username = username;
  123.           this._password = password;
  124.           this._passwordTried = false;
  125.         }
  126.     },
  127.  
  128.     clearAuthentication: function(){
  129.         this._useAuth = false;
  130.     },
  131.  
  132.     get serverUrl() { return this._serverUrl; },
  133.  
  134.     // Internal copy of the status
  135.     _status: null,
  136.     _errorMsg: null,
  137.     _listener: null,
  138.     _seenStart: false,
  139.  
  140.     asyncCall: function(listener, context, methodName, methodArgs, count) {
  141.         debug('asyncCall');
  142.         // Check for call in progress.
  143.         if (this._inProgress)
  144.             throw Components.Exception('Call in progress!');
  145.  
  146.         // Check for the server URL;
  147.         if (!this._serverUrl)
  148.             throw Components.Exception('Not initilized');
  149.  
  150.         this._inProgress = true;
  151.  
  152.         // Clear state.
  153.         this._status = null;
  154.         this._errorMsg = null;
  155.         this._listener = listener;
  156.         this._seenStart = false;
  157.         this._context = context;
  158.         
  159.         debug('Arguments: ' + methodArgs);
  160.  
  161.         // Generate request body
  162.         var xmlWriter = new XMLWriter();
  163.         this._generateRequestBody(xmlWriter, methodName, methodArgs);
  164.  
  165.         var requestBody = xmlWriter.data;
  166.  
  167.         debug('Request: ' + requestBody);
  168.  
  169.         var chann = this._getChannel(requestBody);
  170.  
  171.         // And...... call!
  172.         chann.asyncOpen(this, context);
  173.     },
  174.  
  175.     // Return a HTTP channel ready for POSTing.
  176.     _getChannel: function(request) {
  177.         // Set up channel.
  178.         var ioService = getService('@mozilla.org/network/io-service;1',
  179.             'nsIIOService');
  180.  
  181.         var chann = ioService.newChannelFromURI(this._serverUrl)
  182.             .QueryInterface(Components.interfaces.nsIHttpChannel);
  183.  
  184.         // Create a stream out of the request and attach it to the channel
  185.         var upload = chann.QueryInterface(Components.interfaces.nsIUploadChannel);
  186.         var postStream = createInstance('@mozilla.org/io/string-input-stream;1',
  187.             'nsIStringInputStream');
  188.         postStream.setData(request, request.length);
  189.         upload.setUploadStream(postStream, 'text/xml', -1);
  190.  
  191.         // Set the request method. setUploadStream guesses the method,
  192.         // so we gotta do this afterwards.
  193.         chann.requestMethod = 'POST';
  194.  
  195.         chann.notificationCallbacks = this;
  196.  
  197.         return chann;
  198.     },
  199.  
  200.     // Flag indicating wether or not we are calling the server.
  201.     _inProgress: false,
  202.     get inProgress() { return this._inProgress; },
  203.  
  204.     // nsIStreamListener interface, so's we know about the pending request.
  205.     onStartRequest: function(channel, ctxt) { 
  206.         debug('Start Request') 
  207.     }, // Do exactly nada.
  208.  
  209.     // End of the request
  210.     onStopRequest: function(channel, ctxt, status) {
  211.         debug('Stop Request');
  212.         if (!this._inProgress) return; // No longer interested.
  213.  
  214.         this._inProgress = false;
  215.         this._parser = null;
  216.         
  217.         if (status) {
  218.             debug('Non-zero status: (' + status.toString(16) + ') ');
  219.             this._status = status;
  220.             this._errorMsg = errorMsg;
  221.             try {
  222.                 this._listener.onError(this, ctxt, status,
  223.                                        status.toString(16));
  224.             } catch (ex) {
  225.                 debug('Exception in listener.onError: ' + ex);
  226.             }
  227.             return;
  228.         }
  229.  
  230.         // All done.
  231.         debug('Parse finished');
  232.         if (this._foundFault) {
  233.             try {
  234.                 this._fault = createInstance(XMLRPCFAULT_CONTRACTID,
  235.                     'nsIXmlRpcFault');
  236.                 this._fault.init(this._result.getValue('faultCode').data,
  237.                     this._result.getValue('faultString').data);
  238.                 this._result = null;
  239.             } catch(e) {
  240.                 this._fault = null;
  241.                 this._result = null;
  242.                 throw Components.Exception('Could not parse response');
  243.                 try { 
  244.                     this._listener.onError(this, ctxt, 
  245.                         Components.results.NS_ERROR_FAIL, 
  246.                         'Server returned invalid Fault');
  247.                 }
  248.                 catch(ex) {
  249.                     debug('Exception in listener.onError: ' + ex);
  250.                 }
  251.             }
  252.             debug('Fault: ' + this._fault);
  253.             try { this._listener.onFault(this, ctxt, this._fault); }
  254.             catch(ex) {
  255.                 debug('Exception in listener.onFault: ' + ex);
  256.             }
  257.         } else {
  258.             debug('Result: ' + this._result);
  259.             try { 
  260.                 this._listener.onResult(this, ctxt, this._result);
  261.             } catch (ex) {
  262.                 debug('Exception in listener.onResult: ' + ex);
  263.             }
  264.         }
  265.     },
  266.  
  267.     _parser: null,
  268.     _foundFault: false,
  269.     
  270.     // Houston, we have data.
  271.     onDataAvailable: function(channel, ctxt, inStr, sourceOffset, count) {
  272.         debug('Data available (' + sourceOffset + ', ' + count + ')');
  273.         if (!this._inProgress) return; // No longer interested.
  274.  
  275.         if (!this._seenStart) {
  276.             // First time round
  277.             this._seenStart = true;
  278.  
  279.             // Store request status and message.
  280.             channel = channel
  281.                 .QueryInterface(Components.interfaces.nsIHttpChannel);
  282.             this._responseStatus = channel.responseStatus;
  283.             this._responseString = channel.responseString;
  284.  
  285.             // Check for a 200 response.
  286.             if (channel.responseStatus != 200) {
  287.                 this._status = Components.results.NS_ERROR_FAILURE;
  288.                 this._errorMsg = 'Server returned unexpected status ' +
  289.                     channel.responseStatus;
  290.                 this._inProgress = false;
  291.                 try {
  292.                     this._listener.onError(this, ctxt,
  293.                         Components.results.NS_ERROR_FAILURE,
  294.                         'Server returned unexpected status ' +
  295.                             channel.responseStatus);
  296.                 } catch (ex) {
  297.                     debug('Exception in listener.onError: ' + ex);
  298.                 }
  299.                 return;
  300.             }
  301.  
  302.             // check content type
  303.             if (channel.contentType != 'text/xml') {
  304.                 this._status = Components.results.NS_ERROR_FAILURE;
  305.                 this._errorMsg = 'Server returned unexpected content-type ' +
  306.                     channel.contentType;
  307.                 this._inProgress = false;
  308.                 try {
  309.                     this._listener.onError(this, ctxt,
  310.                         Components.results.NS_ERROR_FAILURE,
  311.                         'Server returned unexpected content-type ' +
  312.                             channel.contentType);
  313.                 } catch (ex) {
  314.                     debug('Exception in listener.onError: ' + ex);
  315.                 }
  316.                 return;
  317.             }
  318.  
  319.             debug('Viable response. Let\'s parse!');
  320.             debug('Content length = ' + channel.contentLength);
  321.             
  322.             this._parser = new SimpleXMLParser(toScriptableStream(inStr),
  323.                 channel.contentLength);
  324.             this._parser.setDocumentHandler(this);
  325.  
  326.             // Make sure state is clean
  327.             this._valueStack = [];
  328.             this._currValue = null;
  329.             this._cdata = null;
  330.             this._foundFault = false;
  331.         }
  332.         
  333.         debug('Cranking up the parser, window = ' + count);
  334.         try {
  335.             this._parser.parse(count);
  336.         } catch(ex) {
  337.             debug('Parser exception: ' + ex);
  338.             this._status = ex.result;
  339.             this._errorMsg = ex.message;
  340.             try {
  341.                 this._listener.onError(this, ctxt, ex.result, ex.message);
  342.             } catch(ex) {
  343.                 debug('Exception in listener.onError: ' + ex);
  344.             }
  345.             this._inProgress = false;
  346.             this._parser = null;
  347.         }
  348.  
  349.     },
  350.  
  351.     _fault: null,
  352.     _result: null,
  353.     _responseStatus: null,
  354.     _responseString: null,
  355.  
  356.     get fault() { return this._fault; },
  357.     get result() { return this._result; },
  358.     get responseStatus() { return this._responseStatus; },
  359.     get responseString() { return this._responseString; },
  360.  
  361.     /* Convenience. Create an appropriate XPCOM object for a given type */
  362.     INT:      1,
  363.     BOOLEAN:  2,
  364.     STRING:   3,
  365.     DOUBLE:   4,
  366.     DATETIME: 5,
  367.     ARRAY:    6,
  368.     STRUCT:   7,
  369.     BASE64:   8, // Not part of nsIXmlRpcClient interface, internal use.
  370.     createType: function(type, uuid) {
  371.         const SUPPORTSID = '@mozilla.org/supports-';
  372.         switch(type) {
  373.             case this.INT:
  374.                 uuid.value = Components.interfaces.nsISupportsPRInt32
  375.                 return createInstance(SUPPORTSID + 'PRInt32;1',
  376.                     'nsISupportsPRInt32');
  377.  
  378.             case this.BOOLEAN:
  379.                 uuid.value = Components.interfaces.nsISupportsPRBool
  380.                 return createInstance(SUPPORTSID + 'PRBool;1',
  381.                     'nsISupportsPRBool');
  382.  
  383.             case this.STRING:
  384.                 uuid.value = Components.interfaces.nsISupportsCString
  385.                 return createInstance(SUPPORTSID + 'cstring;1',
  386.                     'nsISupportsCString');
  387.  
  388.             case this.DOUBLE:
  389.                 uuid.value = Components.interfaces.nsISupportsDouble
  390.                 return createInstance(SUPPORTSID + 'double;1',
  391.                     'nsISupportsDouble');
  392.  
  393.             case this.DATETIME:
  394.                 uuid.value = Components.interfaces.nsISupportsPRTime
  395.                 return createInstance(SUPPORTSID + 'PRTime;1',
  396.                     'nsISupportsPRTime');
  397.  
  398.             case this.ARRAY:
  399.                 uuid.value = Components.interfaces.nsISupportsArray
  400.                 return createInstance(SUPPORTSID + 'array;1',
  401.                     'nsISupportsArray');
  402.  
  403.             case this.STRUCT:
  404.                 uuid.value = Components.interfaces.nsIDictionary
  405.                 return createInstance('@mozilla.org/dictionary;1', 
  406.                     'nsIDictionary');
  407.  
  408.             default: throw Components.Exception('Unsupported type');
  409.         }
  410.     },
  411.  
  412.     // nsISupports interface
  413.     QueryInterface: function(iid) {
  414.         if (!iid.equals(Components.interfaces.nsISupports) &&
  415.             !iid.equals(XMLRPCCLIENT_IID) &&
  416.             !iid.equals(Components.interfaces.nsIXmlRpcClientListener) &&
  417.             !iid.equals(Components.interfaces.nsIRequestObserver) &&
  418.             !iid.equals(Components.interfaces.nsIStreamListener) &&
  419.             !iid.equals(Components.interfaces.nsIInterfaceRequestor))
  420.             throw Components.results.NS_ERROR_NO_INTERFACE;
  421.         return this;
  422.     },
  423.  
  424.     // nsIInterfaceRequester interface
  425.     getInterface: function(iid, result){
  426.         if (iid.equals(Components.interfaces.nsIAuthPrompt)){
  427.             return this;
  428.         }
  429.         Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  430.         return null;
  431.     },
  432.  
  433.     // nsIAuthPrompt interface
  434.     _passwordTried: false,
  435.     promptUsernameAndPassword: function(dialogTitle, text, passwordRealm,
  436.                                         savePassword, user, pwd){
  437.  
  438.         if (this._useAuth){
  439.             if (this._passwordTried){
  440.                 try { 
  441.                     this._listener.onError(this, ctxt, 
  442.                         Components.results.NS_ERROR_FAIL, 
  443.                         'Server returned invalid Fault');
  444.                 }
  445.                 catch(ex) {
  446.                     debug('Exception in listener.onError: ' + ex);
  447.                 }
  448.                 return false;
  449.             }
  450.             user.value = this._username;
  451.             pwd.value = this._password;
  452.             this._passwordTried = true;
  453.             return true;
  454.         }
  455.         return false;
  456.     },
  457.  
  458.     /* Generate the XML-RPC request body */
  459.     _generateRequestBody: function(writer, methodName, methodArgs) {
  460.         writer.startElement('methodCall');
  461.  
  462.         writer.startElement('methodName');
  463.         writer.write(methodName);
  464.         writer.endElement('methodName');
  465.  
  466.         writer.startElement('params');
  467.         for (var i in methodArgs) {
  468.             writer.startElement('param');
  469.             this._generateArgumentBody(writer, methodArgs[i]);
  470.             writer.endElement('param');
  471.         }
  472.         writer.endElement('params');
  473.  
  474.         writer.endElement('methodCall');
  475.     },
  476.  
  477.     /* Write out a XML-RPC parameter value */
  478.     _generateArgumentBody: function(writer, obj) {
  479.         writer.startElement('value');
  480.         var sType = this._typeOf(obj);
  481.         switch (sType) {
  482.             case 'PRUint8':
  483.             case 'PRUint16':
  484.             case 'PRInt16':
  485.             case 'PRInt32':
  486.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  487.                     sType]);
  488.                 writer.startElement('i4');
  489.                 writer.write(obj.toString());
  490.                 writer.endElement('i4');
  491.                 break;
  492.  
  493.             case 'PRBool':
  494.                 obj=obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  495.                 writer.startElement('boolean');
  496.                 writer.write(obj.data ? '1' : '0');
  497.                 writer.endElement('boolean');
  498.                 break;
  499.  
  500.             case 'Char':
  501.             case 'CString':
  502.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  503.                     sType]);
  504.                 writer.startElement('string');
  505.                 writer.write(obj.toString());
  506.                 writer.endElement('string');
  507.                 break;
  508.  
  509.             case 'Float':
  510.             case 'Double':
  511.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  512.                     sType]);
  513.                 writer.startElement('double');
  514.                 writer.write(obj.toString());
  515.                 writer.endElement('double');
  516.                 break;
  517.  
  518.             case 'PRTime':
  519.                 obj = obj.QueryInterface(
  520.                     Components.interfaces.nsISupportsPRTime);
  521.                 var date = new Date(obj.data)
  522.                 writer.startElement('dateTime.iso8601');
  523.                 writer.write(iso8601Format(date));
  524.                 writer.endElement('dateTime.iso8601');
  525.                 break;
  526.                 
  527.             case 'InputStream':
  528.                 obj = obj.QueryInterface(Components.interfaces.nsIInputStream);
  529.                 obj = toScriptableStream(obj);
  530.                 writer.startElement('base64');
  531.                 streamToBase64(obj, writer);
  532.                 writer.endElement('base64');
  533.                 break;
  534.             
  535.             case 'Array':
  536.                 obj = obj.QueryInterface(
  537.                     Components.interfaces.nsISupportsArray);
  538.                 writer.startElement('array');
  539.                 writer.startElement('data');
  540.                 for (var i = 0; i < obj.Count(); i++)
  541.                     this._generateArgumentBody(writer, obj.GetElementAt(i));
  542.                 writer.endElement('data');
  543.                 writer.endElement('array');
  544.                 break;
  545.  
  546.             case 'Dictionary':
  547.                 obj = obj.QueryInterface(Components.interfaces.nsIDictionary);
  548.                 writer.startElement('struct');
  549.                 var keys = obj.getKeys({});
  550.                 for (var k in keys) {
  551.                     writer.startElement('member');
  552.                     writer.startElement('name');
  553.                     writer.write(keys[k]);
  554.                     writer.endElement('name');
  555.                     this._generateArgumentBody(writer, obj.getValue(keys[k]));
  556.                     writer.endElement('member');
  557.                 }
  558.                 writer.endElement('struct');
  559.                 break;
  560.  
  561.             default:
  562.                 throw Components.Exception('Unsupported argument', null, null,
  563.                     obj);
  564.         }
  565.  
  566.         writer.endElement('value');
  567.     },
  568.  
  569.     /* Determine type of a nsISupports primitive, array or dictionary. */
  570.     _typeOf: function(obj) {
  571.         // XPConnect alows JS to pass in anything, because we are a regular
  572.         // JS object to it. So we have to test rigorously.
  573.         if (typeof obj != 'object') return 'Unknown';
  574.  
  575.         // Anything else not nsISupports is not allowed.
  576.         if (typeof obj.QueryInterface != 'function') return 'Unknown';
  577.  
  578.         // Now we will have to eliminate by trying all possebilities.
  579.         try {
  580.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint8);
  581.             return 'PRUint8';
  582.         } catch(e) {}
  583.         
  584.         try {
  585.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint16);
  586.             return 'PRUint16';
  587.         } catch(e) {}
  588.         
  589.         try {
  590.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt16);
  591.             return 'PRInt16';
  592.         } catch(e) {}
  593.         
  594.         try {
  595.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt32);
  596.             return 'PRInt32';
  597.         } catch(e) {}
  598.         
  599.         try {
  600.             obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  601.             return 'PRBool';
  602.         } catch(e) {}
  603.         
  604.         try {
  605.             obj.QueryInterface(Components.interfaces.nsISupportsChar);
  606.             return 'Char';
  607.         } catch(e) {}
  608.         
  609.         try {
  610.             obj.QueryInterface(Components.interfaces.nsISupportsCString);
  611.             return 'CString';
  612.         } catch(e) {}
  613.         
  614.         try {
  615.             obj.QueryInterface(Components.interfaces.nsISupportsFloat);
  616.             return 'Float';
  617.         } catch(e) {}
  618.         
  619.         try {
  620.             obj.QueryInterface(Components.interfaces.nsISupportsDouble);
  621.             return 'Double';
  622.         } catch(e) {}
  623.         
  624.         try {
  625.             obj.QueryInterface(Components.interfaces.nsISupportsPRTime);
  626.             return 'PRTime';
  627.         } catch(e) {}
  628.         
  629.         try {
  630.             obj.QueryInterface(Components.interfaces.nsIInputStream);
  631.             return 'InputStream';
  632.         } catch(e) {}
  633.         
  634.         try {
  635.             obj.QueryInterface(Components.interfaces.nsISupportsArray);
  636.             return 'Array';
  637.         } catch(e) {}
  638.         
  639.         try {
  640.             obj.QueryInterface(Components.interfaces.nsIDictionary);
  641.             return 'Dictionary';
  642.         } catch(e) {}
  643.         
  644.         // Not a supported type
  645.         return 'Unknown';
  646.     },
  647.  
  648.     // Response parsing state
  649.     _valueStack: [],
  650.     _currValue: null,
  651.     _cdata: null,
  652.  
  653.     /* SAX documentHandler interface (well, sorta) */
  654.     characters: function(chars) {
  655.         if (DEBUGPARSE) debug('character data: ' + chars);
  656.         if (this._cdata == null) return;
  657.         this._cdata += chars;
  658.     },
  659.  
  660.     startElement: function(name) {
  661.         if (DEBUGPARSE) debug('Start element ' + name);
  662.         switch (name) {
  663.             case 'fault':
  664.                 this._foundFault = true;
  665.                 break;
  666.  
  667.             case 'value':
  668.                 var val = new Value();
  669.                 this._valueStack.push(val);
  670.                 this._currValue = val;
  671.                 this._cdata = '';
  672.                 break;
  673.  
  674.             case 'name':
  675.                 this._cdata = '';
  676.                 break;
  677.  
  678.             case 'i4':
  679.             case 'int':
  680.                 this._currValue.type = this.INT;
  681.                 break;
  682.  
  683.             case 'boolean':
  684.                 this._currValue.type = this.BOOLEAN;
  685.                 break;
  686.  
  687.             case 'double':
  688.                 this._currValue.type = this.DOUBLE;
  689.                 break;
  690.  
  691.             case 'dateTime.iso8601':
  692.                 this._currValue.type = this.DATETIME;
  693.                 break;
  694.  
  695.             case 'base64':
  696.                 this._currValue.type = this.BASE64;
  697.                 break;
  698.  
  699.             case 'struct':
  700.                 this._currValue.type = this.STRUCT;
  701.                 break;
  702.  
  703.             case 'array':
  704.                 this._currValue.type = this.ARRAY;
  705.                 break;
  706.         }
  707.     },
  708.  
  709.     endElement: function(name) {
  710.         var val;
  711.         if (DEBUGPARSE) debug('End element ' + name);
  712.         switch (name) {
  713.             case 'value':
  714.                 // take cdata and put it in this value;
  715.                 if (this._currValue.type != this.ARRAY &&
  716.                     this._currValue.type != this.STRUCT) {
  717.                     this._currValue.value = this._cdata;
  718.                     this._cdata = null;
  719.                 }
  720.  
  721.                 // Find out if this is the end value
  722.                 // Note that we treat a struct differently, see 'member'
  723.                 var depth = this._valueStack.length;
  724.                 if (depth < 2 || 
  725.                     this._valueStack[depth - 2].type != this.STRUCT) {
  726.                     val = this._currValue;
  727.                     this._valueStack.pop();
  728.  
  729.                     if (depth < 2) {
  730.                         if (DEBUG) debug('Found result');
  731.                         // This is the top level object
  732.                         this._result = val.value;
  733.                         this._currValue = null;
  734.                     } else {
  735.                         // This is an array element. Add it.
  736.                         this._currValue = 
  737.                             this._valueStack[this._valueStack.length - 1];
  738.                         this._currValue.appendValue(val.value);
  739.                     }
  740.                 }
  741.                 break;
  742.                 
  743.             case 'member':
  744.                 val = this._currValue;
  745.                 this._valueStack.pop();
  746.                 this._currValue = this._valueStack[this._valueStack.length - 1];
  747.                 this._currValue.appendValue(val.value);
  748.                 break;
  749.             
  750.             case 'name':
  751.                 this._currValue.name = this._cdata;
  752.                 this._cdata = null;
  753.                 break;
  754.         }
  755.     }
  756. };
  757.  
  758. /* The XMLWriter class constructor */
  759. function XMLWriter() {
  760.     // We assume for now that all data is already in ISO-8859-1.
  761.     this.data = '<?xml version="1.0" encoding="ISO-8859-1"?>';
  762. }
  763.  
  764. /* The XMLWriter class def */
  765. XMLWriter.prototype = {
  766.     data: '',
  767.     
  768.     startElement: function(element) {
  769.         this.data += '<' + element + '>';
  770.     },
  771.  
  772.     endElement: function(element) {
  773.         this.data += '</' + element + '>';
  774.     },
  775.     
  776.     write: function(text) {
  777.         for (var i = 0; i < text.length; i++) {
  778.             var c = text[i];
  779.             switch (c) {
  780.                 case '<':
  781.                     this.data += '<';
  782.                     break;
  783.                 case '&':
  784.                     this.data += '&';
  785.                     break;
  786.                 default:
  787.                     this.data += c;
  788.             }
  789.         }
  790.     },
  791.  
  792.     markup: function(text) { this.data += text }
  793. };
  794.  
  795. /* The Value class contructor */
  796. function Value() { this.type = this.STRING; };
  797.  
  798. /* The Value class def */
  799. Value.prototype = {
  800.     INT:      nsXmlRpcClient.prototype.INT,
  801.     BOOLEAN:  nsXmlRpcClient.prototype.BOOLEAN,
  802.     STRING:   nsXmlRpcClient.prototype.STRING,
  803.     DOUBLE:   nsXmlRpcClient.prototype.DOUBLE,
  804.     DATETIME: nsXmlRpcClient.prototype.DATETIME,
  805.     ARRAY:    nsXmlRpcClient.prototype.ARRAY,
  806.     STRUCT:   nsXmlRpcClient.prototype.STRUCT,
  807.     BASE64:   nsXmlRpcClient.prototype.BASE64,
  808.     
  809.     _createType: nsXmlRpcClient.prototype.createType,
  810.  
  811.     name: null,
  812.     
  813.     _value: null,
  814.     get value() { return this._value; },
  815.     set value(val) {
  816.         // accepts [0-9]+ or x[0-9a-fA-F]+ and returns the character.
  817.         function entityTrans(substr, code) {
  818.             return String.fromCharCode("0" + code);
  819.         }
  820.         
  821.         switch (this.type) {
  822.             case this.STRING:
  823.                 val = val.replace(/&#([0-9]+);/g, entityTrans);
  824.                 val = val.replace(/&#(x[0-9a-fA-F]+);/g, entityTrans);
  825.                 val = val.replace(/</g, '<');
  826.                 val = val.replace(/>/g, '>');
  827.                 val = val.replace(/&/g, '&');
  828.                 this._value.data = val;
  829.                 break;
  830.         
  831.             case this.BOOLEAN:
  832.                 this._value.data = (val == 1);
  833.                 break;
  834.  
  835.             case this.DATETIME:
  836.                 this._value.data = Date.UTC(val.slice(0, 4), 
  837.                     val.slice(4, 6) - 1, val.slice(6, 8), val.slice(9, 11),
  838.                     val.slice(12, 14), val.slice(15));
  839.                 break;
  840.  
  841.             case this.BASE64:
  842.                 this._value.data = base64ToString(val);
  843.                 break;
  844.  
  845.             default:
  846.                 this._value.data = val;
  847.         }
  848.     },
  849.  
  850.     _type: null,
  851.     get type() { return this._type; },
  852.     set type(type) { 
  853.         this._type = type;
  854.         if (type == this.BASE64) 
  855.             this._value = this._createType(this.STRING, {});
  856.         else this._value = this._createType(type, {});
  857.     },
  858.  
  859.     appendValue: function(val) {
  860.         switch (this.type) {
  861.             case this.ARRAY:
  862.                 this.value.AppendElement(val);
  863.                 break;
  864.  
  865.             case this.STRUCT:
  866.                 this.value.setValue(this.name, val);
  867.                 break;
  868.         }
  869.     }
  870. };
  871.  
  872. /* The SimpleXMLParser class constructor 
  873.  * This parser is specific to the XML-RPC format!
  874.  * It assumes tags without arguments, in lowercase.
  875.  */
  876. function SimpleXMLParser(instream, contentLength) {
  877.     this._stream = new PushbackInputStream(instream);
  878.     this._maxlength = contentLength;
  879. }
  880.  
  881. /* The SimpleXMLParser class def */
  882. SimpleXMLParser.prototype = {
  883.     _stream: null,
  884.     _docHandler: null,
  885.     _bufferSize: 256,
  886.     _parsed: 0,
  887.     _maxlength: 0,
  888.     _window: 0, // When async on big documents, release after windowsize.
  889.  
  890.     setDocumentHandler: function(handler) { this._docHandler = handler; },
  891.  
  892.     parse: function(windowsize) {
  893.         this._window += windowsize;
  894.         
  895.         this._start();
  896.     },
  897.  
  898.     // Guard maximum length
  899.     _read: function(length) {
  900.         length = Math.min(this._available(), length);
  901.         if (!length) return '';
  902.         var read = this._stream.read(length);
  903.         this._parsed += read.length;
  904.         return read;
  905.     },
  906.     _unread: function(data) {
  907.         this._stream.unread(data);
  908.         this._parsed -= data.length;
  909.     },
  910.     _available: function() {
  911.         return Math.min(this._stream.available(), this._maxAvailable());
  912.     },
  913.     _maxAvailable: function() { return this._maxlength - this._parsed; },
  914.     
  915.     // read length characters from stream, block until we get them.
  916.     _blockingRead: function(length) {
  917.         length = Math.min(length, this._maxAvailable());
  918.         if (!length) return '';
  919.         var read = '';
  920.         while (read.length < length) read += this._read(length - read.length);
  921.         return read;
  922.     },
  923.  
  924.     // read until the the 'findChar' character appears in the stream.
  925.     // We read no more than _bufferSize characters, and return what we have
  926.     // found so far, but no more than up to 'findChar' if found.
  927.     _readUntil: function(findChar) {
  928.         var read = this._blockingRead(this._bufferSize);
  929.         var pos = read.indexOf(findChar.charAt(0));
  930.         if (pos > -1) {
  931.             this._unread(read.slice(pos + 1));
  932.             return read.slice(0, pos + 1);
  933.         }
  934.         return read;
  935.     },
  936.  
  937.     // Skip stream until string end is found.
  938.     _skipUntil: function(end) {
  939.         var read = '';
  940.         while (this._maxAvailable()) {
  941.             read += this._readUntil(end.charAt(0)) + 
  942.                 this._blockingRead(end.length - 1);
  943.             var pos = read.indexOf(end);
  944.             if (pos > -1) {
  945.                 this._unread(read.slice(pos + end.length));
  946.                 return;
  947.             }
  948.             read = read.slice(-(end.length)); // make sure don't miss our man.
  949.         }
  950.         return;
  951.     },
  952.  
  953.     _buff: '',
  954.     // keep track of whitespce, so's we can discard it.
  955.     _killLeadingWS: false,
  956.     _trailingWS: '',
  957.     
  958.     _start: function() {
  959.         // parse until exhausted. Note that we only look at a window
  960.         // of max. this._bufferSize. Also, parsing of comments, PI's and
  961.         // CDATA isn't as solid as it could be. *shrug*, XML-RPC responses
  962.         // are 99.99% of the time generated anyway.
  963.         // We don't check well-formedness either. Errors in tags will
  964.         // be caught at the doc handler.
  965.         ParseLoop: while (this._maxAvailable() || this._buff) {
  966.             // Check for window size. We stop parsing until more comes
  967.             // available (only in async parsing).
  968.             if (this._window < this._maxlength && 
  969.                 this._parsed >= this._window) 
  970.                 return;
  971.         
  972.             this._buff += this._read(this._bufferSize - this._buff.length);
  973.             this._buff = this._buff.replace('\r\n', '\n');
  974.             this._buff = this._buff.replace('\r', '\n');
  975.             
  976.             var startTag = this._buff.indexOf('<');
  977.             var endTag;
  978.             if (startTag > -1) {
  979.                 if (startTag > 0) { // We have character data.
  980.                     var chars = this._buff.slice(0, startTag);
  981.                     chars = chars.replace(/[ \t\n]*$/, '');
  982.                     if (chars && this._killLeadingWS)
  983.                         chars = chars.replace(/^[ \t\n]*/, '');
  984.                     if (chars) {
  985.                         // Any whitespace previously marked as trailing is in
  986.                         // fact in the middle. Prepend.
  987.                         chars = this._trailingWS + chars;
  988.                         this._docHandler.characters(chars);
  989.                     }
  990.                     this._buff = this._buff.slice(startTag);
  991.                     this._trailingWS = '';
  992.                     this._killLeadingWS = false;
  993.                 }
  994.  
  995.                 // Check for a PI
  996.                 if (this._buff.charAt(1) == '?') {
  997.                     endTag = this._buff.indexOf('?>');
  998.                     if (endTag > -1) this._buff = this._buff.slice(endTag + 2);
  999.                     else {
  1000.                         // Make sure we don't miss '?' at the end of the buffer
  1001.                         this._unread(this._buff.slice(-1));
  1002.                         this._buff = '';
  1003.                         this._skipUntil('?>');
  1004.                     }
  1005.                     this._killLeadingWS = true;
  1006.                     continue;
  1007.                 }
  1008.  
  1009.                 // Check for a comment
  1010.                 if (this._buff.slice(0, 4) == '<!--') {
  1011.                     endTag = this._buff.indexOf('-->');
  1012.                     if (endTag > -1) this._buff = this._buff.slice(endTag + 3);
  1013.                     else {
  1014.                         // Make sure we don't miss '--' at the end of the buffer
  1015.                         this._unread(this._buff.slice(-2));
  1016.                         this._buff = '';
  1017.                         this._skipUntil('-->');
  1018.                     }
  1019.                     this._killLeadingWS = true;
  1020.                     continue;
  1021.                 }
  1022.  
  1023.                 // Check for CDATA
  1024.                 // We only check the first four characters. Anything longer and
  1025.                 // we'd miss it and it would be recognized as a corrupt element
  1026.                 // Anything shorter will be missed by the element scanner as
  1027.                 // well. Next loop we'll have more characters to do a better
  1028.                 // match.
  1029.                 if (this._buff.slice(0, 4) == '<![C') {
  1030.                     // We need to be sure. If we have less than
  1031.                     // 9 characters in the buffer, we can't _be_ sure.
  1032.                     if (this._buff.length < 9 && this._maxAvailable()) continue;
  1033.  
  1034.                     if (this._buff.slice(0, 9) != '<![CDATA[')
  1035.                         throw Components.Exception('Error parsing response');
  1036.                     
  1037.                     endTag = this._buff.indexOf(']]>');
  1038.                     if (endTag > -1) {
  1039.                         this._buff = this._buff.slice(endTag + 3);
  1040.                         this._docHandler.characters(this._buff.slice(9, 
  1041.                             endTag));
  1042.                         this._killLeadingWS = true;
  1043.                         continue;
  1044.                     }  
  1045.                     
  1046.                     // end not in stream. Hrmph
  1047.                     this._docHandler.characters(this._buff.slice(9));
  1048.                     this._buff = '';
  1049.                     while(this._maxAvailable()) {
  1050.                         this._buff += this._readUntil(']') +
  1051.                             this._blockingRead(2);
  1052.                         // Find end.
  1053.                         var pos = this._buff.indexOf(']]>');
  1054.                         // Found.
  1055.                         if (pos > -1) {
  1056.                             this._docHandler.characters(this._buff.slice(0, 
  1057.                                 pos));
  1058.                             this._buff = this._buff.slice(pos + 3);
  1059.                             this._killLeadingWS = true;
  1060.                             continue ParseLoop;
  1061.                         }
  1062.                         // Not yet found. Last 2 chars could be part of end.
  1063.                         this._docHandler.characters(this._buff.slice(0, -2));
  1064.                         this._buff = this._buff.slice(-2); 
  1065.                     }
  1066.  
  1067.                     if (this._buff) // Uhoh. No ]]> found before EOF.
  1068.                         throw Components.Exception('Error parsing response');
  1069.  
  1070.                     continue;
  1071.                 }
  1072.  
  1073.                 // Check for a DOCTYPE decl.
  1074.                 if (this._buff.slice(0, 4) == '<!DO') {
  1075.                     if (this._buff.length < 9 && this.maxAvailable()) continue;
  1076.  
  1077.                     if (this._buff.slice(0, 9) != '<!DOCTYPE')
  1078.                         throw Components.Exception('Error parsing response');
  1079.                     
  1080.                     // Look for markup decl.
  1081.                     var startBrace = this._buff.indexOf('[');
  1082.                     if (startBrace > -1) {
  1083.                         this._unread(this._buff.slice(startBrace + 1));
  1084.                         this._buff = '';
  1085.                         this._skipUntil(']');
  1086.                         this._skipUntil('>');
  1087.                         this._killLeadingWS = true;
  1088.                         continue;
  1089.                     }
  1090.  
  1091.                     endTag = this._buff.indexOf('>');
  1092.                     if (endTag > -1) {
  1093.                         this._buff = this._buff.slice(endTag + 1);
  1094.                         this._killLeadingWS = true;
  1095.                         continue;
  1096.                     }
  1097.  
  1098.                     this._buff = '';
  1099.                     while(this._available()) {
  1100.                         this._buff = this._readUntil('>');
  1101.  
  1102.                         startBrace = this._buff.indexOf('[');
  1103.                         if (startBrace > -1) {
  1104.                             this._unread(this._buff.slice(startBrace + 1));
  1105.                             this._buff = '';
  1106.                             this._skipUntil(']');
  1107.                             this._skipUntil('>');
  1108.                             this._killLeadingWS = true;
  1109.                             continue ParseLoop;
  1110.                         }
  1111.  
  1112.                         endTag = this._buff.indexOf('>');
  1113.                         if (endTag > -1) {
  1114.                             this._buff = this._buff.slice(pos + 1);
  1115.                             this._killLeadingWS = true;
  1116.                             continue;
  1117.                         }
  1118.                     }
  1119.  
  1120.                     if (this._buff)
  1121.                         throw Components.Exception('Error parsing response');
  1122.  
  1123.                     continue;
  1124.                 }
  1125.             
  1126.                 endTag = this._buff.indexOf('>');
  1127.                 if (endTag > -1) {
  1128.                     var tag = this._buff.slice(1, endTag);
  1129.                     this._buff = this._buff.slice(endTag + 1);
  1130.                     tag = tag.replace(/[ \t\n]+.*?(\/?)$/, '$1');
  1131.  
  1132.                     // XML-RPC tags are pretty simple.
  1133.                     if (/[^a-zA-Z0-9.\/]/.test(tag))
  1134.                         throw Components.Exception('Error parsing response');
  1135.  
  1136.                     // Determine start and/or end tag.
  1137.                     if (tag.charAt(tag.length - 1) == '/') {
  1138.                         this._docHandler.startElement(tag.slice(0, -1));
  1139.                         this._docHandler.endElement(tag.slice(0, -1));
  1140.                     } else if (tag.charAt(0) == '/') {
  1141.                         this._docHandler.endElement(tag.slice(1));
  1142.                     } else {
  1143.                         this._docHandler.startElement(tag);
  1144.                     }
  1145.                     this._killLeadingWS = true; 
  1146.                 } else {
  1147.                     // No end tag. Check for window size to avoid an endless
  1148.                     // loop here.. hackish, I know, but if we get here this is
  1149.                     // not a XML-RPC request..
  1150.                     if (this._buff.length >= this._bufferSize)
  1151.                         throw Components.Exception('Error parsing response');
  1152.                     // If we get here and all what is to be read has
  1153.                     // been readinto the buffer, we have an incomplete stream.
  1154.                     if (!this._maxAvailable())
  1155.                         throw Components.Exception('Error parsing response');
  1156.                 }
  1157.             } else {
  1158.                 if (this._killLeadingWS) {
  1159.                     this._buff = this._buff.replace(/^[ \t\n]*/, '');
  1160.                     if (this._buff) this._killLeadingWS = false;
  1161.                 } else {
  1162.                     // prepend supposed trailing whitespace to the front.
  1163.                     this._buff = this._trailingWS + this._buff;
  1164.                     this._trailingWS = '';
  1165.                 }
  1166.  
  1167.                 // store trailing whitespace, and only hand it over
  1168.                 // the next time round. Unless we hit a tag, then we kill it
  1169.                 if (this._buff) {
  1170.                     this._trailingWS = this._buff.match(/[ \t\n]*$/);
  1171.                     this._buff = this._buff.replace(/[ \t\n]*$/, '');
  1172.                 }
  1173.  
  1174.                 if (this._buff) this._docHandler.characters(this._buff);
  1175.  
  1176.                 this._buff = '';
  1177.             }
  1178.         }
  1179.     }
  1180. };
  1181.                 
  1182.  
  1183. /* The PushbackInputStream class constructor */
  1184. function PushbackInputStream(stream) {
  1185.     this._stream = stream;
  1186. }
  1187.  
  1188. /* The PushbackInputStream class def */
  1189. PushbackInputStream.prototype = {
  1190.     _stream: null,
  1191.     _read_characters: '',
  1192.  
  1193.     available: function() {
  1194.         try {
  1195.             return this._read_characters.length + this._stream.available();
  1196.         } catch (e) {
  1197.             return this._read_characters.length;
  1198.         }
  1199.     },
  1200.  
  1201.     read: function(length) {
  1202.         var read;
  1203.         if (this._read_characters.length >= length) {
  1204.             read = this._read_characters.slice(0, length);
  1205.             this._read_characters = this._read_characters.slice(length);
  1206.             return read;
  1207.         } else {
  1208.             read = this._read_characters;
  1209.             this._read_characters = '';
  1210.             return read + this._stream.read(length - read.length);
  1211.         }
  1212.     },
  1213.  
  1214.     unread: function(chars) { 
  1215.         this._read_characters = chars + this._read_characters;
  1216.     }
  1217. };
  1218.             
  1219. /*
  1220.  * Objects
  1221.  */
  1222.  
  1223. /* nsXmlRpcClient Module (for XPCOM registration) */
  1224. var nsXmlRpcClientModule = {
  1225.     registerSelf: function(compMgr, fileSpec, location, type) {
  1226.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1227.  
  1228.         compMgr.registerFactoryLocation(XMLRPCCLIENT_CID, 
  1229.                                         'XML-RPC Client JS component', 
  1230.                                         XMLRPCCLIENT_CONTRACTID, 
  1231.                                         fileSpec,
  1232.                                         location, 
  1233.                                         type);
  1234.         compMgr.registerFactoryLocation(XMLRPCFAULT_CID, 
  1235.                                         'XML-RPC Fault JS component', 
  1236.                                         XMLRPCFAULT_CONTRACTID, 
  1237.                                         fileSpec,
  1238.                                         location, 
  1239.                                         type);
  1240.     },
  1241.  
  1242.     getClassObject: function(compMgr, cid, iid) {
  1243.         if (!cid.equals(XMLRPCCLIENT_CID) && !cid.equals(XMLRPCFAULT_CID))
  1244.             throw Components.results.NS_ERROR_NO_INTERFACE;
  1245.  
  1246.         if (!iid.equals(Components.interfaces.nsIFactory))
  1247.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1248.  
  1249.         if (cid.equals(XMLRPCCLIENT_CID))
  1250.             return nsXmlRpcClientFactory
  1251.         else return nsXmlRpcFaultFactory;
  1252.     },
  1253.  
  1254.     canUnload: function(compMgr) { return true; }
  1255. };
  1256.  
  1257. /* nsXmlRpcClient Class Factory */
  1258. var nsXmlRpcClientFactory = {
  1259.     createInstance: function(outer, iid) {
  1260.         if (outer != null)
  1261.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1262.     
  1263.         if (!iid.equals(XMLRPCCLIENT_IID) &&
  1264.             !iid.equals(Components.interfaces.nsISupports))
  1265.             throw Components.results.NS_ERROR_INVALID_ARG;
  1266.  
  1267.         return new nsXmlRpcClient();
  1268.     }
  1269. }
  1270.  
  1271. /* nsXmlRpcFault Class Factory */
  1272. var nsXmlRpcFaultFactory = {
  1273.     createInstance: function(outer, iid) {
  1274.         if (outer != null)
  1275.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1276.  
  1277.         if (!iid.equals(XMLRPCFAULT_IID) &&
  1278.             !iid.equals(Components.interfaces.nsISupports))
  1279.             throw Components.results.NS_ERROR_INVALID_ARG;
  1280.  
  1281.         return new nsXmlRpcFault();
  1282.     }
  1283. }
  1284.  
  1285. /*
  1286.  * Functions
  1287.  */
  1288.  
  1289. /* module initialisation */
  1290. function NSGetModule(comMgr, fileSpec) { return nsXmlRpcClientModule; }
  1291.  
  1292. /* Create an instance of the given ContractID, with given interface */
  1293. function createInstance(contractId, intf) {
  1294.     return Components.classes[contractId]
  1295.         .createInstance(Components.interfaces[intf]);
  1296. }
  1297.  
  1298. /* Get a pointer to a service indicated by the ContractID, with given interface */
  1299. function getService(contractId, intf) {
  1300.     return Components.classes[contractId].getService(Components.interfaces[intf]);
  1301. }
  1302.  
  1303. /* Convert an inputstream to a scriptable inputstream */
  1304. function toScriptableStream(input) {
  1305.     var SIStream = Components.Constructor(
  1306.         '@mozilla.org/scriptableinputstream;1',
  1307.         'nsIScriptableInputStream', 'init');
  1308.     return new SIStream(input);
  1309. }
  1310.  
  1311. /* format a Date object into a iso8601 datetime string, UTC time */
  1312. function iso8601Format(date) {
  1313.     var datetime = date.getUTCFullYear();
  1314.     var month = String(date.getUTCMonth() + 1);
  1315.     datetime += (month.length == 1 ?  '0' + month : month);
  1316.     var day = date.getUTCDate();
  1317.     datetime += (day < 10 ? '0' + day : day);
  1318.  
  1319.     datetime += 'T';
  1320.  
  1321.     var hour = date.getUTCHours();
  1322.     datetime += (hour < 10 ? '0' + hour : hour) + ':';
  1323.     var minutes = date.getUTCMinutes();
  1324.     datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
  1325.     var seconds = date.getUTCSeconds();
  1326.     datetime += (seconds < 10 ? '0' + seconds : seconds);
  1327.  
  1328.     return datetime;
  1329. }
  1330.  
  1331. /* Convert a stream to Base64, writing it away to a string writer */
  1332. const BASE64CHUNK = 255; // Has to be devidable by 3!!
  1333. function streamToBase64(stream, writer) {
  1334.     while (stream.available()) {
  1335.         var data = [];
  1336.         while (data.length < BASE64CHUNK && stream.available()) {
  1337.             var d = stream.read(1).charCodeAt(0);
  1338.             // reading a 0 results in NaN, compensate.
  1339.             data = data.concat(isNaN(d) ? 0 : d);
  1340.         }
  1341.         writer.write(toBase64(data));
  1342.     }
  1343. }
  1344.  
  1345. /* Convert data (an array of integers) to a Base64 string. */
  1346. const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
  1347.     '0123456789+/';
  1348. const base64Pad = '=';
  1349. function toBase64(data) {
  1350.     var result = '';
  1351.     var length = data.length;
  1352.     var i;
  1353.     // Convert every three bytes to 4 ascii characters.
  1354.     for (i = 0; i < (length - 2); i += 3) {
  1355.         result += toBase64Table[data[i] >> 2];
  1356.         result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  1357.         result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
  1358.         result += toBase64Table[data[i+2] & 0x3f];
  1359.     }
  1360.  
  1361.     // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
  1362.     if (length%3) {
  1363.         i = length - (length%3);
  1364.         result += toBase64Table[data[i] >> 2];
  1365.         if ((length%3) == 2) {
  1366.             result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  1367.             result += toBase64Table[(data[i+1] & 0x0f) << 2];
  1368.             result += base64Pad;
  1369.         } else {
  1370.             result += toBase64Table[(data[i] & 0x03) << 4];
  1371.             result += base64Pad + base64Pad;
  1372.         }
  1373.     }
  1374.  
  1375.     return result;
  1376. }
  1377.  
  1378. /* Convert Base64 data to a string */
  1379. const toBinaryTable = [
  1380.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
  1381.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
  1382.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
  1383.     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
  1384.     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
  1385.     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
  1386.     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
  1387.     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
  1388. ];
  1389. function base64ToString(data) {
  1390.     var result = '';
  1391.     var leftbits = 0; // number of bits decoded, but yet to be appended
  1392.     var leftdata = 0; // bits decoded, bt yet to be appended
  1393.  
  1394.     // Convert one by one.
  1395.     for (var i in data) {
  1396.         var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
  1397.         var padding = (data[i] == base64Pad);
  1398.         // Skip illegal characters and whitespace
  1399.         if (c == -1) continue;
  1400.         
  1401.         // Collect data into leftdata, update bitcount
  1402.         leftdata = (leftdata << 6) | c;
  1403.         leftbits += 6;
  1404.  
  1405.         // If we have 8 or more bits, append 8 bits to the result
  1406.         if (leftbits >= 8) {
  1407.             leftbits -= 8;
  1408.             // Append if not padding.
  1409.             if (!padding)
  1410.                 result += String.fromCharCode((leftdata >> leftbits) & 0xff);
  1411.             leftdata &= (1 << leftbits) - 1;
  1412.         }
  1413.     }
  1414.  
  1415.     // If there are any bits left, the base64 string was corrupted
  1416.     if (leftbits)
  1417.         throw Components.Exception('Corrupted base64 string');
  1418.  
  1419.     return result;
  1420. }
  1421.  
  1422. if (DEBUG) debug = function(msg) { 
  1423.     dump(' -- XML-RPC client -- : ' + msg + '\n'); 
  1424. };
  1425. else debug = function() {}
  1426.  
  1427. // vim:sw=4:sr:sta:et:sts:
  1428.